Medium 好讀版點此。
在 Day 5 文章中,我們知道要形成一個 commit 前,要先下 git add 指令把檔案放到預存區(staging area)。
所以預存區是什麼?為什麼不直接 commit 就好?
根據 git 官方文件,預存區的存在是要讓使用者可以不必每次都把所有修改過的檔案都放到 commit 裡,而是可以只取其中一部分,這樣的設計可避免彼此不相關的修改被無條件放進同一個 commit 裡。

預存區的機制讓我們可以把無關的修改放到兩個 commit 中
有了預存區的機制,我們將得以透過不整理 commit 的方式(要整理 commit 也不是不行,但相對複雜一些),把一次大改動拆成許多小部分,再一一放進不同的 commit,讓每個 commit 快照追蹤到的變化更為細緻。
在學習上層瓷器指令時,我們知道可以用 git status 來看預存區的狀態。
例如現在我們透過以下指令生成 hello_world.txt 檔案:
echo "Hello, world!" > hello_world.txt
經過 git add 指令被放到預存區,用 git status 來看就會變這樣:

在區塊 1 中,hello_world.txt 尚未被放到預存區,表示 git 還不知道這份檔案,目前仍是未被追蹤(untracked)狀態,標示紅字。
在區塊 2 中,hello_world.txt 透過 git add 指令被放到預存區。
在區塊 3 中,透過 git status 可發現 hello_world.txt 變成綠色,表示已經被放到預存區,git 已經知道這份檔案、為被追蹤(tracked)狀態,可以接著進入下一次的 commit。
透過上述觀察,我們發現下完 git add 指令後,主要發生兩件事:
現在我們來觀察 .git/ 資料夾,看看在 git add 之後,發生了什麼變化。
原本只有經 git init 的 .git/ 資料夾結構長這樣:
.git/
├── hooks/
│ ├── applypatch-msg.sample
│ └── ...
│
├── info/
│ └── exclude
│
├── objects/
│ ├── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ └── tags/
│
├── config
├── description
└── HEAD
經過 git add 後,發生了兩個變化:
.git/
├── hooks/
│ ├── applypatch-msg.sample
│ └── ...
│
├── info/
│ └── exclude
│
├── objects/ # 多了一個af/資料夾,內含38碼檔案
│ ├── af/
│ │ └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│ ├── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ └── tags/
│
├── config
├── description
├── HEAD
└── index # 多一個index檔案,這裡就是預存區(staging area)
先來看看新增的 index 檔案,這個 index 檔案就是預存區!如果單純打開會看到亂碼:
這是因為裡面存的是二進制資料,得用其他指令打開,看當中由 0、1 組成的內容,但轉成那樣我們依然不會看懂,因此這裡用管路指令 git ls-files --stage 打開,觀察預存區 index 裡的資料:

跑出的結果是 100644 af5626b4a114abcb82d63db7c8082c3c4756e51b 0 hello_world.txt,讓我們一一拆解:
100644:代表檔案模式,100644 表示一個正常的不可執行檔案(也就是一個死的、不能跑也不能當參照路徑的檔案);若為如 .exe 之類的執行檔,則為 100755;而若為表示檔案路徑的符號鏈接(symbolic link),則以 120000 表示。af5626b4a114abcb82d63db7c8082c3c4756e51b:這看起來是一組雜湊碼,而且跟 objects/ 裡面的新資料夾名稱 af/ 與當中檔案 5626b4a114abcb82d63db7c8082c3c4756e51b 組合起來一樣!這個我們稍後來看。0:處理合併衝突時使用,單純下 git add 指令會跑出 0。hello_world.txt:檔案名稱。在 git add 之後,objects/ 還多了一個名為 af/ 的資料夾,裡面有個名為 5626b4a114abcb82d63db7c8082c3c4756e51b 的檔案,這兩者加起來共 40 位數,也存在剛剛觀察的 index 檔案裡面。
objects/ 的意思是物件,這個資料夾確實就是 Day 3 提到的 git 物件,因此我們發現:git add 指令會生成 git 物件,而資料夾名稱加上內部檔案名稱共 40 位數,就是用來識別此物件的雜湊碼。
但 Day 3 提到的 git 物件有三種,這裡是哪種物件呢?如果直接把檔案打開來看,一樣會發現亂碼:
因此改用 git cat-file 這管路指令,如下:
git cat-file -t af5626b:-t 表示查看物件種類,顯示 blob。git cat-file -s af5626b:-s 表示查看物件大小,hello_world.txt 內容是 Hello, world! ,共 13 個字母,一個字母佔 1 位元組,加上結尾的換行符號 \n,因此共 14 位元組,顯示 14。git cat-file -p af5626b:-p 表示查看物件內容,也就是 Hello, world!。
經過 git add 指令,會發生下列兩件事:
objects/ 資料夾。index 出現關於新增之 git 物件雜湊碼等相關資訊。現在我們應該也能看懂 Day 1 及 Day 2 說的底層管路指令在做什麼:
git hash-object:產生一組 blob 物件,並計算物件 ID(也就是其雜湊碼)。git update-index --add:把第 1. 步產生的 blob 物件放進預存區。第 1. 步會影響 .git/objects/ 資料夾、第 2. 步才影響預存區 index 檔案,而上層瓷器指令 git add 則是一次把兩步驟一起完成。
下篇文章,我們將一同探討進一步下 git commit 後, .git/ 資料夾發生的變化。